#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <GL/glew.h>
#include <GL/glut.h>

#include <math.h>

typedef GLfloat point[3];

bool mSwitch = true;
int subSwitch[6] = {4,7,13,23,33,43};
int currentSub = 4;
int currentIndex = 0;
int _axis = 1;
char s[90];

float _rotateTheta[3] = { 0.0, 0.0, 0.0 };

float _vibrateTheta = 0.0;


float PI = 3.1415926;

int lastX = 0;
int lastY = 0;

double omega = 0.01;
int time = 0;

GLuint vShader,fShader;
GLuint program = 0;
GLint timeParam;

GLfloat CtrlPoints[4][4][3] =
{
    {
        {-1.5, -1.5, 0.0},
        {-0.5, -1.5, 0.0},
        {0.5, -1.5, 0.0},
        {1.5, -1.5, 0.0}},
    {
        {-1.5, -0.5, 0.0},
        {-0.5, -0.5, 0.0},
        {0.5, -0.5, 0.0},
        {1.5, -0.5, 0.0}},
    {
        {-0.5, 0.5, 0.0},
        {0.5, 0.5, 0.0},
        {1.5, 0.5, 0.0},
        {2.5, 0.5, 0.0}},
    {
        {-0.5, 1.5, 0.0},
        {0.5, 1.5, 0.0},
        {1.5, 1.5, 0.0},
        {2.5, 1.5, 0.0}}
};

//GLfloat light0_pos[]={0.0, 0.0,4.0, 1.0};

enum MENU_TYPE
{
        MODE_FILL,
		MODE_LINE,
		SUB_0,
		SUB_1,
		SUB_2,
		SUB_3,
		SUB_4,
		SUB_5,
		VSHADER,
		FSHADER,
		EXIT
};

char* readShaderSource(char* shaderFile) {
    FILE* fp = fopen(shaderFile, "r");
    char* buf;
	int size;

	if (fp != NULL) {
        fseek(fp, 0, SEEK_END);
        size = ftell(fp);
        rewind(fp);
	if (size > 0) {
		buf = (char *)malloc(sizeof(char) * (size+1));
		size = fread(buf,sizeof(char),size,fp);
		buf[size] = '\0';
	}
	}
    fclose(fp);
    return buf;
}


void initShader(){
	char *vs = NULL, *fs = NULL;

	//First, create our shaders
	vShader = glCreateShader(GL_VERTEX_SHADER);
	fShader = glCreateShader(GL_FRAGMENT_SHADER);

	//Read in the programs
	vs = readShaderSource((char*)("vSimple_glsl.txt"));
	fs = readShaderSource((char*)("fSimple_glsl.txt"));

	//Setup a few constant pointers for below
	const char * ff = fs;
	const char * vv = vs;

	glShaderSource(vShader, 1, &vv, NULL);
	glShaderSource(fShader, 1, &ff, NULL);

	free(vs);free(fs);

	glCompileShader(vShader);
	glCompileShader(fShader);

	program = glCreateProgram();

    glAttachShader(program, vShader);
    glAttachShader(program, fShader);

	glProgramParameteriEXT(program,GL_GEOMETRY_INPUT_TYPE_EXT,GL_LINES);
	glProgramParameteriEXT(program,GL_GEOMETRY_OUTPUT_TYPE_EXT,GL_LINE_STRIP);

	int status;

    glGetShaderiv(vShader, GL_COMPILE_STATUS, &status);
 //   checkError(status, "Failed to compile the vertex shader.");

    glGetShaderiv(fShader, GL_COMPILE_STATUS, &status);
//    checkError(status, "Failed to compile the fragment shader.");

    glLinkProgram(program);
    glGetShaderiv(program, GL_LINK_STATUS, &status);
 //   checkError(status, "Failed to link the shader program object.");

    glUseProgram(program);

}

typedef struct materialStruct
{
	GLfloat ambient[4];
    GLfloat diffuse[4];
	GLfloat specular[4];
	GLfloat shininess;
}materialStruct;

void materials( materialStruct *materials)
{
	glMaterialfv(GL_FRONT, GL_AMBIENT, materials->ambient);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, materials->diffuse);
	glMaterialfv(GL_FRONT, GL_SPECULAR, materials->specular);
	glMaterialf(GL_FRONT, GL_SHININESS, materials->shininess);
}

materialStruct meshColor =
{
	{0.0,1.0,0.0,1.0},
	{0.8,0.8,0.8,1.0},
	{1.0,1.0,1.0,1.0},
	50,
};

// This tells us if we want lines or fill mode
//this tells us if we want subdivision cycle or not

void initlights(void)
{
    GLfloat ambient[] =
    {0.2, 0.2, 0.2, 1.0};
    GLfloat position[] =
    {0.0, 0.0, 2.0, 1.0};


    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_POSITION, position);

	//materials(&meshColor);

}

void modeSwitch(bool n){
	if(n){
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
	else{
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}
}

void normal(point n, point p, point q, point r)
{
  n[0]=(q[1]-p[1])*(r[2]-p[2])-(q[2]-p[2])*(r[1]-p[1]);
  n[1]=(q[2]-p[2])*(r[0]-p[0])-(q[0]-p[0])*(r[2]-p[2]);
  n[2]=(q[0]-p[0])*(r[2]-p[2])-(q[2]-p[2])*(r[0]-p[0]);
}

void quadrilateral(point p[4][4])
{
	glBegin(GL_LINE_LOOP);
//	glBegin(GL_QUADS);
	point n;
	normal(n, p[0][0], p[3][0], p[3][3]);
//	glNormal3fv(n);
//	glNormal3fv(p[0][0]);
	glVertex3fv(p[0][0]);
//	glNormal3fv(p[3][0]);
	glVertex3fv(p[3][0]);
//	glNormal3fv(c);
	glVertex3fv(p[3][3]);
//	glNormal3fv(d);
	glVertex3fv(p[0][3]);
	glEnd();
}


void divide_curve(point c[4], point r[4], point l[4])
{

/* division of convex hull of Bezier curve */

   int i;
   point t;
   for(i=0;i<3;i++)
   {
       l[0][i]=c[0][i];
       r[3][i]=c[3][i];
       l[1][i]=(c[1][i]+c[0][i])/2;
       r[2][i]=(c[2][i]+c[3][i])/2;
       t[i]=(l[1][i]+r[2][i])/2;
       l[2][i]=(t[i]+l[1][i])/2;
       r[1][i]=(t[i]+r[2][i])/2;
       l[3][i]=r[0][i]=(l[2][i]+r[1][i])/2;
   }
}

void transpose(point a[4][4])
{

  /* transpose wastes time but makes program more readable */

  int i,j, k;
  GLfloat tt;
  for(i=0;i<4;i++) for(j=i;j<4; j++) for(k=0;k<3;k++)
  {
        tt=a[i][j][k];
        a[i][j][k]=a[j][i][k];
        a[j][i][k]=tt;
  }
}

void divide_patch(point p[4][4], int n)
{
   point q[4][4], r[4][4], s[4][4], t[4][4];
   point a[4][4], b[4][4];
   int k;
   if(n==0) quadrilateral(p); /* draw patch if recursion done */

/* subdivide curves in u direction, transpose results, divide
in u direction again (equivalent to subdivision in v) */

   else {
       for(k=0; k<4; k++) divide_curve(p[k], a[k], b[k]);
       transpose(a);
       transpose(b);
       for(k=0; k<4; k++) {
           divide_curve(a[k], q[k], r[k]);
           divide_curve(b[k], s[k], t[k]);
       }

/* recursive division of 4 resulting patches */

       divide_patch(q, n-1);
       divide_patch(r, n-1);
       divide_patch(s, n-1);
       divide_patch(t, n-1);
   }
}

void drawMesh() {


//	glMap2f(GL_MAP2_VERTEX_3, -0.5, 0.5, 3, 4, -0.5, 0.5, 3 * 4, 4, &CtrlPoints[0][0][0]);
//
//	glMapGrid2f(currentSub, 0.0, 1.0, currentSub, 0.0, 1.0);
//
//	glEvalMesh2(GL_FILL, 0, currentSub, 0, currentSub);

	divide_patch(CtrlPoints,5);

}



void init() {

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
//	glShadeModel(GL_SMOOTH);
	glEnable(GL_NORMALIZE);

	glEnable(GL_MAP2_VERTEX_3);
	glEnable(GL_AUTO_NORMAL);
	glEnable(GL_DEPTH_TEST);
}

void display() {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	modeSwitch(mSwitch);

	glTranslatef(0.0, 0.0, -10.0);

	glRotatef(-50, 0.0, 1.0, 0.0);
	glRotatef(_rotateTheta[1], 0.0, 0.1, 0.0);
    initlights();

	drawMesh();

	glutSwapBuffers();

}



void spinMesh() {

		_rotateTheta[1] += 0.1;
	//	_SelfRotateTheta[_axis] += 1.1;

	if (_rotateTheta[_axis] > 360.0)
		_rotateTheta[_axis] -= 360.0;

	int j;
	time = glutGet(GLUT_ELAPSED_TIME);
	for(j=0;j<4;j++){

		CtrlPoints[0][j][2] = -0.8*sin(omega*time);
//		CtrlPoints[2][j][2] = -0.2*sin(omega*time);
		CtrlPoints[1][j][2] = 0.6*sin(omega*time);
		CtrlPoints[3][j][2] = -0.2*sin(omega*time);

	}
	glUniform1f(timeParam, (GLfloat) glutGet(GLUT_ELAPSED_TIME));

	glutPostRedisplay();
}


void myReshape(int w, int h) {
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);//Applies subsequent matrix operations to the modelview matrix stack. this is used to contril the
	glLoadIdentity();//glLoadIdentity replaces the current matrix with the identity matrix.
	//glOrtho(-2.0, 2.0, -2.0, 2.0, 1.0, -5.0);
	gluPerspective(50.0, (float) w / (float) h, 2.0, 20.0);

	glMatrixMode(GL_MODELVIEW);//Applies subsequent matrix operations to the projection matrix stack.

}


void mouse(int btn, int state, int x, int y) {
	lastX=x;
	lastY=y;

}

void mouseMove(int x, int y){

	if(lastX!=x)
		_rotateTheta[1]+=(lastX-x);

	lastX = x;
	lastY = y;
}

void keyPress(unsigned char key, int x, int y){
	if(key =='f' || key =='F'){
		mSwitch = !mSwitch;
	}
	if(key =='s' || key =='S'){
		if(currentIndex==5)
			currentIndex=0;
		else
			currentIndex++;
		currentSub = subSwitch[currentIndex];
	}
}


void processMenuEvents(int order){
	switch (order)
        {
        case EXIT:
			glutDestroyWindow(glutGetWindow());
			exit(0);
			break;
		case GL_FILL:
			mSwitch = true;
			break;
		case GL_LINE:
			mSwitch = false;
			break;
		case SUB_0:
			currentSub = subSwitch[0];
			currentIndex = 0;
			break;
		case SUB_1:
			currentSub = subSwitch[1];
			currentIndex = 1;
			break;
		case SUB_2:
			currentSub = subSwitch[2];
			currentIndex = 2;
			break;
		case SUB_3:
			currentSub = subSwitch[3];
			currentIndex = 3;
			break;
		case SUB_4:
			currentSub = subSwitch[4];
			currentIndex = 4;
			break;
		case SUB_5:
			currentSub = subSwitch[5];
			currentIndex = 5;
			break;
        default:
            break;
        }

}


void glutCreateMenu(){
	// Create a menu
    int menu,submenu1,submenu2, submenu3;

	submenu1 = glutCreateMenu(processMenuEvents);
	glutAddMenuEntry("GL_FILL",MODE_FILL);
	glutAddMenuEntry("GL_LINE",MODE_LINE);

	submenu2 = glutCreateMenu(processMenuEvents);
	glutAddMenuEntry("0 subdivisions",SUB_0);
	glutAddMenuEntry("1 subdivisions",SUB_1);
	glutAddMenuEntry("2 subdivisions",SUB_2);
	glutAddMenuEntry("3 subdivisions",SUB_3);
	glutAddMenuEntry("4 subdivisions",SUB_4);
	glutAddMenuEntry("5 subdivisions",SUB_5);

	submenu3 = glutCreateMenu(processMenuEvents);
	glutAddMenuEntry("vertex shader",VSHADER);
	glutAddMenuEntry("fragment shader",FSHADER);

	menu = glutCreateMenu(processMenuEvents);
	glutAddSubMenu("glPolygonMode",submenu1);
	glutAddSubMenu("SubdivisionMode",submenu2);
	glutAddSubMenu("Shader Lighting",submenu3);
	glutAddMenuEntry("Exit",EXIT);

	glutAttachMenu(GLUT_RIGHT_BUTTON);
}

int main(int argc, char** argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
//	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH) - windowWidth) / 2,
//			(glutGet(GLUT_SCREEN_HEIGHT) - windowHeight) / 2); // Set the windows centered in screen based on native resolution.

	glutCreateWindow("Assignment 3");

	init();


	glutReshapeFunc(myReshape);
	glutDisplayFunc(display);
	glutIdleFunc(spinMesh);
	glutMouseFunc(mouse);
	glutMotionFunc(mouseMove);
	glutKeyboardFunc(keyPress);
	glutCreateMenu();

	glewInit();
//	if (glewIsSupported("GL_VERSION_2_1"))
//		printf("Ready for OpenGL 2.1\n");
//	else {
//		printf("OpenGL 2.1 not supported\n");
//		exit(1);
//	}
//	if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader && GL_EXT_geometry_shader4)
//		printf("Ready for GLSL - vertex, fragment, and geometry units\n");
//	else {
//		printf("Not totally ready :( \n");
//		exit(1);
//	}
//	initShader();
	glutMainLoop();
	return 0;
}

